{
 "cells": [
  {
   "cell_type": "markdown",
   "source": [
    "# 操作输入和输出\n",
    "RunnableParallel可以用于操纵一个Runnable的输出，以匹配序列中下一个Runnable的输入格式。\n",
    "prompt的输入应该是一个带有\"context\"和\"question\"键的映射。用户输入只是问题。所以我们需要使用我们的检索器获取上下文，并将用户输入通过\"question\"键传递。代码如下："
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "176c37bab6a049ef"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "\"['Harrison worked at Kensho.', '哈里森曾在Kensho工作。']\""
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import os\n",
    "from operator import itemgetter\n",
    "\n",
    "from dotenv import load_dotenv\n",
    "from langchain_community.embeddings import DashScopeEmbeddings\n",
    "from langchain_community.llms.tongyi import Tongyi\n",
    "from langchain_community.vectorstores import FAISS\n",
    "from langchain_core.output_parsers import StrOutputParser\n",
    "from langchain_core.prompts import ChatPromptTemplate\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "vectorstore = FAISS.from_texts(\n",
    "    [\"harrison worked at kensho\"], embedding=DashScopeEmbeddings(\n",
    "        dashscope_api_key=os.getenv(\"DASHSCOPE_API_KEY\"))\n",
    ")\n",
    "\n",
    "retriever = vectorstore.as_retriever()\n",
    "\n",
    "template = \"\"\"\n",
    "    Answer the question based only on the following context:\n",
    "{context}\n",
    "\n",
    "Question: {question}\n",
    "\n",
    "Answer in the following language: {language}\n",
    "    \"\"\"\n",
    "\n",
    "prompt = ChatPromptTemplate.from_template(template)\n",
    "# 请注意，我们将max_retries = 0设置为避免在RateLimits等情况下重试\n",
    "llm1 = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\",\n",
    "    max_retries=0,\n",
    ")\n",
    "#\n",
    "llm2 = Tongyi()\n",
    "# 添加回退\n",
    "\n",
    "chain = {\n",
    "            \"context\": retriever, \"question\": RunnablePassthrough(), \"language\": RunnablePassthrough()\n",
    "        } | prompt | llm2 | StrOutputParser()\n",
    "chain.invoke([\"Who worked at Kensho?\", \"中文\"])\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-11-11T03:20:14.899916Z",
     "start_time": "2024-11-11T03:20:10.044590Z"
    }
   },
   "id": "d8e10e33cc4bfcbf",
   "execution_count": 1
  },
  {
   "cell_type": "markdown",
   "source": [
    "::: {.callout-tip} 请注意，当将RunnableParallel与另一个Runnable组合时，我们甚至不需要将字典包装在RunnableParallel类中 - 类型转换会为我们处理。在链的上下文中，这些是等效的： :::\n",
    "```python\n",
    "{\"context\": retriever, \"question\": RunnablePassthrough()}\n",
    "```\n",
    "或\n",
    "```python\n",
    "RunnableParallel({\"context\": retriever, \"question\": RunnablePassthrough()})\n",
    "```\n",
    "或\n",
    "```python\n",
    "RunnableParallel(context=retriever, question=RunnablePassthrough())\n",
    "```\n",
    "\n",
    "## 使用itemgetter\n",
    "使用Python的`itemgetter`从映射中提取数据，与`RunnableParallel`结合使用。有关itemgetter的更多信息，请参阅[Python文档](https://docs.python.org/3/library/operator.html#operator.itemgetter)。\n",
    "\n",
    "使用itemgetter从映射中提取特定的键实例：\n",
    ">为什么列1中传递多个参数时需要使用List，而不能使用字段？\n",
    "需使用itemgetter将单个传入参数解析为多个参数。"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "4506eae373b8974b"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "'哈里森曾在肯肖工作。'"
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "chain = {\n",
    "            \"context\": itemgetter(\"question\") | retriever,\n",
    "            \"question\": itemgetter(\"question\"),\n",
    "            \"language\": itemgetter(\"language\"),\n",
    "        } | prompt | llm2 | StrOutputParser()\n",
    "\n",
    "\n",
    "chain.invoke({\"question\": \"Who worked at Kensho?\", \"language\": \"中文\"})"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-28T09:41:04.157621Z",
     "start_time": "2024-10-28T09:41:02.980168Z"
    }
   },
   "id": "f80c68ab1765d6f2",
   "execution_count": 10
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 并行化步骤\n",
    "RunnableParallel（又名RunnableMap）可以轻松地并行执行多个Runnables，并将这些Runnables的输出作为映射返回。\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "63961c139f87d46f"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "data": {
      "text/plain": "{'joke': AIMessage(content='当然，这里有一个轻松的猫咪笑话给你：\\n\\n为什么猫不喜欢玩扑克牌？\\n因为每次它们想数数的时候，就会发现少了“一只”（指“一对”，在中文里可以幽默地理解为真的少了一只猫）！\\n\\n希望这个小笑话能让你会心一笑！', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 61, 'prompt_tokens': 24, 'total_tokens': 85, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-b3bd483f-7e5a-4220-a662-f7ceea977e47-0', usage_metadata={'input_tokens': 24, 'output_tokens': 61, 'total_tokens': 85, 'input_token_details': {}, 'output_token_details': {}}),\n 'poem': AIMessage(content='猫咪轻步踏月光，  \\n夜色中眼如星亮。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 16, 'prompt_tokens': 29, 'total_tokens': 45, 'completion_tokens_details': None, 'prompt_tokens_details': None}, 'model_name': 'qwen-max', 'system_fingerprint': None, 'finish_reason': 'stop', 'logprobs': None}, id='run-a6dcbe5c-e76d-4987-9a4e-b6c04a5129bd-0', usage_metadata={'input_tokens': 29, 'output_tokens': 16, 'total_tokens': 45, 'input_token_details': {}, 'output_token_details': {}})}"
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\n",
    "from langchain_core.runnables import RunnableParallel\n",
    "from dotenv import load_dotenv\n",
    "import os\n",
    "\n",
    "load_dotenv()\n",
    "\n",
    "llm = ChatOpenAI(\n",
    "    # 若没有配置环境变量，请用百炼API Key将下行替换为：api_key=\"sk-xxx\",\n",
    "    openai_api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    openai_api_base=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    "    model_name=\"qwen-max\"\n",
    ")\n",
    "\n",
    "chain1 = ChatPromptTemplate.from_template(\"告诉我一个关于{topic}的笑话\") | llm\n",
    "chain2 = ChatPromptTemplate.from_template(\"写一首关于{topic}的短诗(2行)\") | llm\n",
    "\n",
    "map_chain = RunnableParallel(joke=chain1, poem=chain2)\n",
    "# %time combined.invoke({\"topic\": \"猫\"})\n",
    "map_chain.invoke({\"topic\": \"猫\"})"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-28T09:51:25.822461Z",
     "start_time": "2024-10-28T09:51:17.942460Z"
    }
   },
   "id": "25e1a086231e1f5c",
   "execution_count": 15
  },
  {
   "cell_type": "markdown",
   "source": [
    "## 并行性\n",
    "RunnableParallel还可用于并行运行独立的进程，因为映射中的每个Runnable都是并行执行的。例如，我们可以看到我们之前的joke_chain，poem_chain和map_chain的运行时间大致相同，即使map_chain执行了这两个Runnable。\n"
   ],
   "metadata": {
    "collapsed": false
   },
   "id": "b2576c68db01e311"
  },
  {
   "cell_type": "code",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "7.83 s ± 3.9 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "1.66 s ± 733 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)\n",
      "The slowest run took 4.10 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
      "6.3 s ± 3.33 s per loop (mean ± std. dev. of 7 runs, 1 loop each)\n"
     ]
    }
   ],
   "source": [
    "%timeit chain1.invoke({\"topic\": \"bear\"})\n",
    "%timeit chain2.invoke({\"topic\": \"bear\"})\n",
    "%timeit map_chain.invoke({\"topic\": \"bear\"})\n"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2024-10-28T09:53:40.027557Z",
     "start_time": "2024-10-28T09:51:30.084497Z"
    }
   },
   "id": "14a9a13722f71944",
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "outputs": [],
   "source": [],
   "metadata": {
    "collapsed": false
   },
   "id": "77585d5afdfcd2c3"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
